package network

import (
	"bytes"
	"encoding/binary"
	"errors"
	"log"
	"net"
	"os"
	"strconv"
	"strings"
	"sync"
	"tcptemplate/config"
	"tcptemplate/logger"
	"tcptemplate/util"

	"time"

	"golang.org/x/net/context"
	"golang.org/x/net/websocket"
)

type ConnectionContext struct {
	context.Context
	sockId     string
	Type       string          //连接类型，有websocket，普通的socket
	Cnntime    int64           //连接时间
	IpAdress   string          //客户端ip
	Port       int32           //端口号
	Cnn        net.Conn        //连接主体代码
	wsCnn      *websocket.Conn //
	Business   interface{}     //业务代码，最好是一个结构体的形式.
	h          HandleFunction
	SecureAuth bool   // secure tcp 是否已经协商秘钥
	Pri        []byte // secure tcp协商的私钥
}

type ConnectionManager struct {
	allCount int32 //在线连接
	dayCount int32 //当天在线的用户量,累计的数据
	connSet  map[string]ConnectionContext
	mutex    sync.Mutex
	wg       sync.WaitGroup
}

func withTimeoutConnectContext(parent context.Context, timeOut time.Duration) ConnectionContext {
	ctx, _ := context.WithTimeout(parent, timeOut)
	if config.IfSecure() {

	}
	ret := ConnectionContext{ctx, "0", "socket", 0, "", 0, nil, nil, nil, DefaultPackageHandler, false, []byte{}}
	return ret
}

func withCalcelConnectContext(parent context.Context, timeOut time.Duration) ConnectionContext {
	ctx, _ := context.WithCancel(parent)
	ret := ConnectionContext{ctx, "0", "socket", 0, "", 0, nil, nil, nil, DefaultPackageHandler, false, []byte{}}
	return ret
}

var gConnMgr *ConnectionManager

func init() {
	gConnMgr = new(ConnectionManager)
	gConnMgr.connSet = make(map[string]ConnectionContext, 1000)
	gConnMgr.mutex = sync.Mutex{}
}

func SendPackage(ctx ConnectionContext, payload []byte, length int) error {
	var header PackHeader
	header.Identify[0] = 0x40
	header.Identify[1] = 0x41
	header.Length = 4
	packbuf := bytes.Buffer{}

	buf := bytes.NewBuffer([]byte{})
	e := binary.Write(buf, binary.BigEndian, header.Length)
	if e != nil {
		log.Println(e.Error())
	}

	for _, v := range buf.Bytes() {
		header.Verify += v
	}
	//log.Println(header.Verify)
	err := binary.Write(&packbuf, binary.BigEndian, header.Identify)
	err = binary.Write(&packbuf, binary.BigEndian, header.Length)
	err = binary.Write(&packbuf, binary.BigEndian, header.Verify)
	if err != nil {
		log.Println(err.Error())
	}
	b := []byte{}
	b = append(b, packbuf.Bytes()...)
	b = append(b, payload[0:length]...)
	log.Println(b)
	_, e = (ctx.Cnn).Write(b)
	if e != nil {
		log.Println(e.Error())
		return e
	}
	return nil
}

func GetConnMgr() *ConnectionManager {
	return gConnMgr
}

//todo bad desgins ,will be changed soon
func (this *ConnectionContext) BindBusiness(clientData interface{}) {
	this.Business = clientData
}

func (this *ConnectionManager) GetCount() int32 {
	return this.allCount
}

func (this *ConnectionManager) AddConn(connection ConnectionContext) error {
	this.mutex.Lock()
	this.wg.Add(1)
	this.allCount += 1
	if _, ok := this.connSet[connection.sockId]; ok {
		this.mutex.Unlock()
		return errors.New("connection existed")
	} else {
		this.mutex.Unlock()
		this.connSet[connection.sockId] = connection
		return nil
	}
}

func (this *ConnectionManager) CheckIfExisted(connection ConnectionContext) bool {
	//判断当前的ip地址段是否已经存在，用于判断例如网游中的多客户端的情况。
	if _, ok := this.connSet[connection.sockId]; ok {
		return true
	} else {
		return false
	}
}

func (this *ConnectionManager) DeleteConn(sockid string) error {
	this.mutex.Lock()
	this.wg.Done()
	this.allCount -= 1
	if _, ok := this.connSet[sockid]; ok {
		delete(this.connSet, sockid)
	} else {
		this.mutex.Unlock()
		return errors.New("connection not existed")
	}
	this.mutex.Unlock()
	return nil
}

//restart service
func (this *ConnectionManager) Restart() {
	e := os.Setenv(util.ENV_GRACEFUL_RESTART, "true")
	if nil != e {
		logger.LogDebugError(e.Error())
	}
}

func GetIpAddr(ip string) string {
	s := strings.Split(ip, ":")
	if s != nil {
		return s[0]
	}
	return ""
}

func GetPort(ipaddr string) int32 {
	s := strings.Split(ipaddr, ":")
	if s != nil {
		ret, e := strconv.Atoi(s[1])
		if e != nil {
			log.Println(e.Error())
			return -1
		}
		return int32(ret)
	}
	return -1
}
